#include <iostream>
#include <stdio.h>
#include <string.h>

using std::cout;
using std::endl;
using std::string;

//关键点：
//1、有两段，一段计数器，一段字符
//计数器位于堆，且和pstr放在一起，放在前面
//每次多申请4字节
//指针指向字符段，有需要时再往前移动
//
//2、写时复制，读时不复制
//使用重载的=进行写操作，重载的<<进行写操作
//采用内部类，双层友元
//重载的[]返回类型为内部类
class String{
public:
    String()
    : _pstr(new char[5]() +4)
    {
        cout<<"无参构造"<<endl;
       *(int*)(_pstr-4)=1;
       //为了一次操作4个字节，转为int型指针
    }

    String(const char *pstr)
    : _pstr(new char[strlen(pstr)+5]+4)
    {
        cout<<"有参构造"<<endl;
        strcpy(_pstr,pstr);
        *(int*)(_pstr-4)=1;
    }

    String(const String &rhs)
    : _pstr(rhs._pstr)
    {
        cout<<"拷贝"<<endl;
        ++*(int*)(_pstr-4);
    }

    String &operator=(const String &rhs){
        cout<<"赋值"<<endl;
        if(&rhs != this){
            if(--*(int*)(_pstr-4)==0){
                delete [](_pstr-4); //如果自己是最后一个，则释放整条空间
                cout<<"自己是最后一个，释放原有空间再改变指向"<<endl;
            }

            _pstr=rhs._pstr;
            ++*(int*)(_pstr-4); //此时_pstr已经指向了新字符段
        }
        return *this;//如果是自己赋给自己，直接返回即可
    }

    ~String(){
        cout<<"析构"<<endl;
        //cout<<_pstr<<endl;
        if(--*(int*)(_pstr-4)==0){
            delete [](_pstr-4);
            //cout<<_pstr<<endl;
        }
    }

    const char *c_str() const{
        return _pstr;
        //返回字符段
        //因为这是String，不是string
    }

    int getCount(){
        return *(int*)(_pstr-4);
        //返回计数值
    }

private:
    class charProxy
    {
        public:
            charProxy(String &self,size_t idx)
            : _self(self)   
              //这里同样存在问题，因为传进来的self是const的，绑定不了
              //而如果进行写操作，会改变_self，
              //所以一开始设计时_self只能是非const的，那么只能把传进来的self去掉const
            , _idx(idx)
            {

            }
            char &operator=(const char &ch);

            operator char(){
                //  使用类型转换函数
                //  因为最终是 cout<<s2[0]，s2[0]如果是char就可以直接输出
                //  而不用去重载左边是ostream，右边是charProxy的情况
                return _self._pstr[_idx];
            }

        private:
            // String _self;  目前为止，String还是不完整的，不能在这里创建对象
            // 所以只能创建指向String的指针或者引用
            String &_self;  //引用数据成员，必须在一开始就初始化赋值
            size_t _idx;
    };

public:
    charProxy operator[](size_t idx){   //String *const this
        return charProxy(*this, idx);
        //能传入的参数只有this和idx，
        //所以在charProxy中，设置这两个数据成员
        //最终由它们找到对应下标
    }


    size_t size(){
        return strlen(_pstr);
        //返回字符段长度
    }

    friend std::ostream &operator<<(std::ostream &os,const String &rhs);

private:
    char *_pstr;
};

std::ostream &operator<<(std::ostream &os,const String &rhs){
    if(rhs._pstr){
        os<<rhs._pstr;
    }
    return os;
}

//写操作
char &String::charProxy::operator=(const char &ch){
    if(_idx<_self.size()){
        if(_self.getCount()>1){
            char *ptmp=new char[strlen(_self._pstr)+5]()+4;
            strcpy(ptmp,_self._pstr);
            --*(int*)(_self._pstr-4);
            _self._pstr=ptmp;
            *(int*)(_self._pstr-4)=1;
        }

        _self._pstr[_idx] = ch;
        return _self._pstr[_idx];//独享时，直接返回即可，不需要写时复制
    }
    else{
        static char nullchar='\0';
        return nullchar;
    }
}


void test(){

    String s1("hello");
    cout<<"s1的整条大小="<<s1.size()<<endl;
    cout<<"count="<<s1.getCount()<<endl;
    String s2=s1;
    cout<<"count=="<<s2.getCount()<<endl;
    cout<<s1<<endl;
    cout<<s2<<endl;

    cout<<"~~~~~~~~~~~~~~~~~~~~~~~~~~"<<endl;
    cout<<&s1<<endl;
    cout<<&s2<<endl;
    printf("%p\n",s1.c_str());
    printf("%p\n",s2.c_str());
    //由此可见s1、s2指向同一字段，但是s1、s2自己的地址不同
    //这个字段_pstr位于堆区，计数器在它前面

    cout<<endl;
    String s3("world");
    cout<<s3<<endl;
    printf("%p\n",s3.c_str());

    cout<<endl;
    s3=s1;
    cout<<"count="<<s3.getCount()<<endl;
    cout<<"s1.count="<<s1.getCount()<<endl;
    cout<<"s2.count="<<s2.getCount()<<endl;
    cout<<"s3.count="<<s3.getCount()<<endl;
    cout<<s1<<endl;
    cout<<s2<<endl;
    cout<<s3<<endl;
    printf("%p\n",s1.c_str());
    printf("%p\n",s2.c_str());
    printf("%p\n",s3.c_str());
    cout<<endl;

    cout<<"执行写操作，写时复制"<<endl;
    s3[0]='H';
    cout<<"s1.count="<<s1.getCount()<<endl;
    cout<<"s2.count="<<s2.getCount()<<endl;
    cout<<"s3.count="<<s3.getCount()<<endl;
    cout<<s1<<endl;
    cout<<s2<<endl;
    cout<<s3<<endl;
    printf("%p\n",s1.c_str());
    printf("%p\n",s2.c_str());
    printf("%p\n",s3.c_str());
    
    cout<<"~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~"<<endl;

    cout<<"执行读操作，不应该写时复制"<<endl;
    cout<<"s2[0]="<<s2[0]<<endl;    //  对s2[0]使用类型转换函数，从charProxy-》char
    cout<<"s1.count="<<s1.getCount()<<endl;
    cout<<"s2.count="<<s2.getCount()<<endl;
    cout<<"s3.count="<<s3.getCount()<<endl;
    cout<<s1<<endl;
    cout<<s2<<endl;
    cout<<s3<<endl;
    printf("%p\n",s1.c_str());
    printf("%p\n",s2.c_str());
    printf("%p\n",s3.c_str());


/*
    要区分是读操作还是写操作，只能通过重载运算符
    重载运算符必须有自定义类型，所以把下标访问运算符的返回类型改为charProxy类类型
    写操作是 =，左边是char，右边也是char。把左边返回类型改为charProxy
    读操作是 <<，左边是ostream，右边是char。把右边返回类型改为charProxy
*/

}

int main()
{   
    test();
    return 0;
}

